// tournament.sp - TF2Stern Tournament Structure
//  by Thomas 'nickmame' Roth

/* File Structure:
 *  SternTournamentInit()
 *  SternTournamentClose()
 *  OnMapStart callback
 *  OnMapEnd callback
 *  StartWaiting helper
 *  GetPlayableTeam helper
 *  CheckTournament callback
 *  StartRound callback
 *  StartHumiliation callback
 *  EndTournament callback
 *  RestartTournament callback
 */

/////////////////////////////////////////////////////////////////////////////
// CONSTANTS
/////////////////////////////////////////////////////////////////////////////
enum TournamentState // corresponds to the tournament state diagram
{
  tsWaiting,    // pregame and game over states
  tsCountdown,  // countdown period after both teams ready
  tsTournament, // tournament in-progress
  tsHumiliation // period between rounds
};

enum  // PlayerTeam - TF2 specific team indexes
{
  ptNone, // 0 = unassigned
  ptSpec, // 1 = spectator
  ptRed,  // 2 = red team
  ptBlue  // 3 = blue team
};

#define TEAM_RED      0 // red team index
#define TEAM_BLUE     1 // blue team index
#define INVALID_TEAM -1 // unplayable team index

/////////////////////////////////////////////////////////////////////////////
// GLOBAL VARIABLES
/////////////////////////////////////////////////////////////////////////////
new TournamentState:gtsGameState = tsWaiting;

// current ready status for both teams
new bool:gbReadyState[2] = {false, false};

/////////////////////////////////////////////////////////////////////////////
// SternTournamentInit()
//  hooks the tournament events
/////////////////////////////////////////////////////////////////////////////
SternTournamentInit()
{
  HookEvent("tournament_stateupdate", CheckTournament);
  
  HookEvent("teamplay_round_start", StartRound);
  HookEvent("teamplay_round_win", StartHumiliation);
  HookEvent("teamplay_round_stalemate", StartHumiliation);
  
  HookEvent("teamplay_game_over", EndTournament);
  HookEvent("tf_game_over", EndTournament);
  
  RegServerCmd("mp_tournament_restart", RestartTournament);
  
  // set global variables
  gtsGameState = tsWaiting;
  gbReadyState[TEAM_RED] = false;
  gbReadyState[TEAM_BLUE] = false;
}

/////////////////////////////////////////////////////////////////////////////
// SternTournamentClose()
//  unhooks the tournament events
/////////////////////////////////////////////////////////////////////////////
SternTournamentClose()
{
  UnhookEvent("tournament_stateupdate", CheckTournament);
  
  UnhookEvent("teamplay_round_start", StartRound);
  UnhookEvent("teamplay_round_win", StartHumiliation);
  UnhookEvent("teamplay_round_stalemate", StartHumiliation);
  
  UnhookEvent("teamplay_game_over", EndTournament);
  UnhookEvent("tf_game_over", EndTournament);
}

/////////////////////////////////////////////////////////////////////////////
// OnMapStart callback
//  resets tournament state
/////////////////////////////////////////////////////////////////////////////
public OnMapStart()
{
  StartWaiting();
}

/////////////////////////////////////////////////////////////////////////////
// OnMapEnd callback
//  clears tournament memory
/////////////////////////////////////////////////////////////////////////////
public OnMapEnd()
{
  if( gtsGameState != tsWaiting )
  {
    DeleteOutputFile();
  }
}

/////////////////////////////////////////////////////////////////////////////
// StartWaiting helper
//  resets the game state
/////////////////////////////////////////////////////////////////////////////
static StartWaiting()
{
  // reset the game state
  gtsGameState = tsWaiting;
  gbReadyState[TEAM_RED] = false;
  gbReadyState[TEAM_BLUE] = false;
  WriteDebug(dpLow, "tournament state changed to waiting");
}

/////////////////////////////////////////////////////////////////////////////
// GetPlayableTeam helper
//  converts player team into array index
/////////////////////////////////////////////////////////////////////////////
static GetPlayableTeam(client)
{
  WriteDebug(dpFunc, "called GetPlayableTeam(%d)", client);
  new team = GetClientTeam(client);
  
  // convert team to indexable format
  switch( team )
  {
    case ptRed:   // red team
      team = TEAM_RED;
    case ptBlue:  // blue team
      team = TEAM_BLUE;
    default:      // non-playable team (error)
      team = INVALID_TEAM;
  }
  
  // report team index
  if( team == INVALID_TEAM )
  {
    WriteDebug(dpHigh, "client %d is on an unplayable team", client);
  }
  else
  {
    WriteDebug(dpLow, "client %d is on team %d", client, team);
  }
  
  return team;
}

/////////////////////////////////////////////////////////////////////////////
// CheckTournament callback
//  determines when to begin transition into the tournament state
/////////////////////////////////////////////////////////////////////////////
public CheckTournament(Handle:event, const String:name[], bool:dontBroadcast)
{
  WriteDebug(dpFunc, "called CheckTournament(event)");
  new bool:readystate = GetEventBool(event, "readystate");
  new team = GetPlayableTeam(GetEventInt(event, "userid"));
  
  // check if the ready state changed
  if( GetEventBool(event, "namechange") == false && team != INVALID_TEAM )
  {
    // update team ready state
    gbReadyState[team] = readystate;
    WriteDebug(dpLow, "team %d ready status changed to %d", team, readystate);
    
    // determine if tournament has begun
    if( gbReadyState[TEAM_RED] && gbReadyState[TEAM_BLUE] )
    {
      // both teams ready - enter countdown state
      gtsGameState = tsCountdown;
      WriteDebug(dpLow, "tournament state changed to countdown");
      
      // prepare client data
      OpenOutputFile();
    }
    else
    {
      // team unreadied - leave countdown state
      gtsGameState = tsWaiting;
      WriteDebug(dpLow, "tournament state changed to waiting");
    }
  }
}

/////////////////////////////////////////////////////////////////////////////
// StartRound callback
//  handles transitions into the tournament state
/////////////////////////////////////////////////////////////////////////////
public StartRound(Handle:event, const String:name[], bool:dontBroadcast)
{
  WriteDebug(dpFunc, "called StartRound(event)");
  
  if( gtsGameState == tsCountdown )
  {
    // first round of the game
    gtsGameState = tsTournament;
    WriteDebug(dpLow, "tournament state changed to tournament");
    PrintToChatAll("TF2Stern :: player statistics are being recorded");
  }
  else if( gtsGameState == tsHumiliation )
  {
    // new round of an old game
    gtsGameState = tsTournament;
    WriteDebug(dpLow, "tournament state changed to tournament");
    PrintToChatAll("TF2Stern :: statistics recording has resumed");
  }
}

/////////////////////////////////////////////////////////////////////////////
// StartHumiliation callback
//  pauses statistics gathering during humiliation
/////////////////////////////////////////////////////////////////////////////
public StartHumiliation(Handle:event, const String:name[], bool:dontBroadcast)
{
  WriteDebug(dpFunc, "called StartHumiliation(event)");
  
  // enter humiliation round
  gtsGameState = tsHumiliation;
  WriteDebug(dpLow, "tournament state changed to humiliation");
  PrintToChatAll("TF2Stern :: statistics recording has paused");
}

/////////////////////////////////////////////////////////////////////////////
// EndTournament callback
//  saves client data after tournament concludes
/////////////////////////////////////////////////////////////////////////////
public EndTournament(Handle:event, const String:name[], bool:dontBroadcast)
{
  WriteDebug(dpFunc, "called EndTournament(event)");
  
  // handle client data
  CloseOutputFile();
  
  // reset game start to waiting
  StartWaiting();
}

/////////////////////////////////////////////////////////////////////////////
// RestartTournament callback
//  deletes client data after interruptions
/////////////////////////////////////////////////////////////////////////////
public Action:RestartTournament(args)
{
  WriteDebug(dpFunc, "called RestartTournament(cmd)");
  
  if( gtsGameState != tsWaiting )
  {
    // clear client data
    DeleteOutputFile();
    
    // reset game state to waiting
    StartWaiting();

    PrintToChatAll("TF2Stern :: player statistics have been discarded");
  }
  
  return Plugin_Continue;
}
